<?php


namespace plugins\auto_code\controller;


use app\admin\model\AdminMenuModel;
use cmf\controller\PluginAdminBaseController;
use think\Exception;
use think\facade\Db;
use think\facade\View;
use tree\Tree;

/**
 * 自动生成代码
 * Class AutoController
 * @package plugins\auto_code\controller
 */
class AutoController extends PluginAdminBaseController
{
    /**
     * 默认页面
     * @return mixed|string
     * @throws \Exception
     */
    public function autoIndex()
    {
        $tableName = input('get.table_name/s', '');

        $table = config('database.connections.mysql.database');
        //查询数据库表字段
        $sql = "select COLUMN_COMMENT,COLUMN_NAME,IS_NULLABLE from information_schema.COLUMNS where table_name = '{$tableName}' and table_schema = '{$table}'";
        $columns = Db::query($sql);
        $tree = new Tree();
        $result = Db::name('AdminMenu')->where("status", 1)->order(["list_order" => "ASC"])->select()->toArray();
        $str = "<option value='\$id'>\$spacer \$name</option>";
        $tree->init($result);
        $selectCategory = $tree->getTree(0, $str);
        $this->assign("category", $selectCategory);

        $componentArr = require_once(__DIR__ . '/../component/map.php');

        $this->assign('componentArr', $componentArr);
        $this->assign('columns', $columns);

        return $this->fetch('/auto_index');
    }

    /**
     * 生成方法
     */
    public function gen()
    {
        $info = input('post.info');
        $controller = input('post.controller');
        $model = input('post.model');
        $menu_id = input('post.menu_id');
        $menu_name = input('post.menu_name');

        if (!($info && $controller)) {
            $this->error('非法参数');
        }

        $info = json_decode(htmlspecialchars_decode($info), true);

        $model = $this->camelize(substr($model, strlen(config('database.connections.mysql.prefix'))));

        $componentArr = require_once(__DIR__ . '/../component/map.php');
        $pluginPath = $pluginPath = app()->getRootPath() . 'public/plugins/auto_code/';
        //获取文件/图片
        $fileFields = [];
        foreach ($info as $infoItem) {
            if (in_array($infoItem['type'], ['oneImage', 'multiImage', 'oneFile', 'multiFile', 'oneVideo', 'multiVideo'])) {
                $fileFields[] = $infoItem['field_value'];
            }
        }

        //生成视图模板
        $controller = ucfirst($controller);
        //新增
        list($addViews, $sqlTpl) = $this->addViews($info, $controller, $componentArr);
        //编辑
        $editViews = $this->editViews($info, $controller, $componentArr);
        //默认
        $indexViews = $this->indexViews($info, $controller);
        //生成控制器
        $r1 = $this->controllerAdd(['parent', 'menu_name' => $menu_name, 'controller' => $controller, 'model' => $model, 'plugin_path' => $pluginPath, 'menu_id' => $menu_id, 'sqlTpl' => $sqlTpl]);
        //生成模型
        $r2 = $this->modelAdd(['model' => $model, 'plugin_path' => $pluginPath, 'fileFields' => $fileFields]);
        //生成视图
        $r3 = $this->viewAdd(['controller' => $controller], $indexViews, $addViews, $editViews);

        if ($r1 && $r2 && $r3) {
            //替换admin.js
            $publicPath = app()->getRootPath() . 'public/static/js/admin.js';
            $adminContent = file_get_contents($pluginPath . 'extra/admin.js');
            file_put_contents($publicPath, $adminContent);
            $this->success("生成成功");
        }

        $this->success("生成失败");
    }

    /**
     * 列表视图
     * @param $info
     * @param string $controller
     * @return mixed|string
     * @throws \Exception
     */
    protected function indexViews($info, $controller = '')
    {
        $id = "\$vo['id']";
        //生成fields字段
        $fields = [];

        foreach ($info as $item) {
            //文字隐藏
            if ($item['textOverflow']) {
                $fields[] = "<p onclick='see(this)' style='cursor:pointer;color:dodgerblue;width:160px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;'>{\$vo['{$item['field_value']}']}</p>";
            } elseif (\substr($item['field_value'], -4) == 'time') {// 格式化时间戳
                $fields[] = "{:date('Y-m-d H:i',\$vo['{$item['field_value']}'])}";
            } elseif (in_array($item['type'], ['oneImage', 'oneVideo', 'oneFile', 'multiImage', 'multiVideo', 'multiFile'])) {
                $fields[] = '<button onclick="detail({$vo.id},\'' . $item['field_value'] . '\')" type="button" class="btn btn-info">查看</button>';
            } elseif ($item['type'] == 'checkbox') { //checkbox 多选

                $fields[] = "{\$vo['{$item['field_value']}']}";
            } elseif ($item['type'] == 'selectedBox') { //单选下拉框
                $fields[] = "{\$vo['{$item['field_value']}']}";
            } else { //默认文本
                $fields[] = "{\$vo['{$item['field_value']}']}";
            }
        }

        $this->assign('foreach', '<foreach name="lists[\'data\']" item="vo">');
        $this->assign('foreachEnd', '</foreach>');
        //字段
        $this->assign('columns', $info);
        $this->assign('fields', $fields);
        $this->assign('url', "{:url('$controller/field')}");//控制器
        //列表
        $this->assign('index', "{:url('$controller/index')}");//控制器
        //添加
        $this->assign('add', "{:url('$controller/add')}");//控制器
        //编辑
        $this->assign('edit', "{:url('$controller/edit',['id'=>$id])}");//控制器
        //删除
        $this->assign('delete', "{:url('$controller/del',['id'=>$id])}");//控制器
        //分页
        $this->assign('page', '{$page}');
        $this->assign('head', 'public@header');
        //变量
        $this->assign('q_name', '{$name|default=\'\'}');

        return $this->fetch('/indexViews');
    }

    /**
     * 生成多选框 和下拉框逻辑
     * @param $item
     * @return array
     * @throws Exception
     */
    protected function genCs($item)
    {
        $item['model']['sql'] = strtolower(trim($item['model']['sql']));
        if ($item['model']['sql'] && (!str_starts_with($item['model']['sql'], 'select') ||
                strpos($item['model']['sql'], ';') !== false ||
                strpos($item['model']['sql'], '；') !== false)) {
            throw new Exception('非法sql语句:只能已select语句开头并且不能包含分号');
        }

        $dataType = 1;//sql 2-自定义数据

        $fieldArr = ['', ''];
        $sql = '';

        if ($item['model']['sql']) {
            $sqlArr = array_filter(explode(' ', $item['model']['sql']), function ($v) {
                return !empty(trim($v));
            });

            $fieldArr = explode(',', $sqlArr[1]);

            if (!$fieldArr || count($fieldArr) < 2) {
                throw new Exception('字段必须大于两个');
            }

            $itemArr = Db::query($item['model']['sql']);
            if (!$itemArr) {
                throw new Exception('字段查询失败');
            }
            $keys = [];
            foreach ($itemArr as $it) {
                $tmp_item = array_values($it);
                $keys[] = ['id' => $tmp_item[0], 'name' => $tmp_item[1]];
            }

            $sql = "\${$item['field_value']}_arr = Db::query('{$item['model']['sql']}');\$this->assign('{$item['field_value']}_arr',\${$item['field_value']}_arr);";

        } else {
            $dataType = 2;
            $itemArr = json_decode($item['model']['tpl'], true);

            if (!$itemArr) {
                throw new Exception('字段查询失败');
            }

            $keys = [];
            foreach ($itemArr as $item) {
                $keys[] = ['id' => $item['key'], 'name' => $item['value']];
            }
        }

        return [$dataType, $keys, $fieldArr ? [$fieldArr[0], $fieldArr[1]] : [], $sql];
    }

    /**
     * 生成 新增视图
     * @param $info
     * @param $controllers
     * @param $componentArr
     * @return mixed|string
     * @throws Exception
     */
    protected function addViews($info, $controllers, $componentArr)
    {
        $fields = '';
        $pluginPath = app()->getRootPath() . 'public/plugins/auto_code/';
        $jsTpl = '';
        $sqlTpl = '';
        //生成字段
        foreach ($info as $item) {
            if (in_array($item['field_value'], ['delete_time', 'update_time', 'create_time', 'id'])) {
                continue;
            }
            $jsPath = '';
            $tplPath = $pluginPath . 'component/html/' . $item['type'] . '.html';
            $componentArr[$item['type']]['haveJs'] && $jsPath = $pluginPath . 'component/js/' . $item['type'] . '.js';
            //模板引入
            View::assign('is_edit', false);
            View::assign('field', $item['field_value']);
            View::assign('field_name', $item['field_name']);
            View::assign('pt', $item['placeHolderText'] ?? '');
//            if ($item['type'] == 'oneImage') { //单图上传
//
//            }
            if ($item['type'] == 'checkbox') { //checkbox 多选
                if ((!$item['model']) || (empty($item['model']['sql']) && empty($item['model']['tpl']))) {
                    $this->error('请先在设置里填写要关联的模型');
                }

                list($dataType, $keys, $fields_value, $sql) = $this->genCs($item);
                $sqlTpl .= $sql;

                View::assign('fields_value', $fields_value);
                View::assign('dataType', $dataType);
                View::assign('field_keys', $keys);

            } elseif ($item['type'] == 'selectedBox') { //单选下拉框
                if (!$item['model']) {
                    $this->error('请先在设置里填写要关联的模型');
                }

                list($dataType, $keys, $fields_value, $sql) = $this->genCs($item);
                $sqlTpl .= $sql;

                View::assign('fields_value', $fields_value);
                View::assign('dataType', $dataType);
                View::assign('field_keys', $keys);

            }
            $fields .= View::fetch($tplPath);
            $jsTpl .= ($jsPath ? file_get_contents($jsPath) : '');
        }

        $this->assign('head', 'public@header');
        //列表
        $this->assign('index', "{:url('$controllers/index')}");//控制器
        //添加
        $this->assign('add', "{:url('$controllers/add')}");//控制器
        //保存
        $this->assign('addPost', "{:url('$controllers/addPost')}");//控制器
        //字段
        $this->assign('fields', $fields);
        $this->assign('jsTpl', $jsTpl);
        // $this->assign('is_edit', false);

        return [$this->fetch('/addViews'), $sqlTpl];
    }

    /**
     * 生成编辑视图
     * @param $info
     * @param $controllers
     * @param $componentArr
     * @return mixed|string
     * @throws Exception
     */
    protected function editViews($info, $controllers, $componentArr)
    {
        $fields = '';
        $pluginPath = app()->getRootPath() . 'public/plugins/auto_code/';
        $jsTpl = '';
        //生成字段
        foreach ($info as $item) {
            if (in_array($item['field_value'], ['delete_time', 'update_time', 'create_time', 'id'])) {
                continue;
            }
            $jsPath = '';
            $tplPath = $pluginPath . 'component/html/' . $item['type'] . '.html';
            $componentArr[$item['type']]['haveJs'] && $jsPath = $pluginPath . 'component/js/' . $item['type'] . '.js';
            //模板引入
            View::assign('is_edit', true);
            View::assign('field', $item['field_value']);
            View::assign('field_name', $item['field_name']);
            View::assign('pt', $item['placeHolderText'] ?? '');
//            if ($item['type'] == 'oneImage') { //单图上传
//
//            }
            if ($item['type'] == 'checkbox') { //checkbox 多选
                if ((!$item['model']) || (empty($item['model']['sql']) && empty($item['model']['tpl']))) {
                    $this->error('请先在设置里填写要关联的模型');
                }

                list($dataType, $keys, $fields_value,) = $this->genCs($item);

                View::assign('fields_value', $fields_value);
                View::assign('dataType', $dataType);
                View::assign('field_keys', $keys);

            } elseif ($item['type'] == 'selectedBox') { //单选下拉框
                if (!$item['model']) {
                    $this->error('请先在设置里填写要关联的模型');
                }

                list($dataType, $keys, $fields_value,) = $this->genCs($item);

                View::assign('fields_value', $fields_value);
                View::assign('dataType', $dataType);
                View::assign('field_keys', $keys);

            }
            $fields .= View::fetch($tplPath);
            $jsTpl .= ($jsPath ? file_get_contents($jsPath) : '');
        }

        $this->assign('head', 'public@header');
        $id = '$vo["id"]';
        //列表
        $this->assign('index', "{:url('$controllers/index')}");//控制器
        //编辑
        $this->assign('edit', "{:url('$controllers/edit',array('id'=>$id))}");//控制器
        //保存
        $this->assign('editPost', "{:url('$controllers/editPost')}");//控制器
        //字段
        $this->assign('fields', $fields);
        $this->assign('jsTpl', $jsTpl);
        //$this->assign('is_edit', true);

        return $this->fetch('/editViews');
    }

    /**
     * 在指定位置生成视图文件
     */
    protected function viewAdd($params, $indexView, $addView, $editView): bool
    {
        $dir = $this->uncamelize($params['controller']);
        $viewPath = app()->getRootPath() . 'public/themes/admin_simpleboot3/admin/' . $dir . '/';

        //检查目录
        if (!is_dir($viewPath)) {
            mkdir($viewPath, 0777, true);
        }
        //默认视图
        $index = file_put_contents($viewPath . 'index.html', $indexView);
        //新增视图
        $add = file_put_contents($viewPath . 'add.html', $addView);
        //默认视图
        $edit = file_put_contents($viewPath . 'edit.html', $editView);

        if ($index && $add && $edit) {
            return true;
        }

        return false;
    }

    /**
     * 生成控制器
     */
    protected function controllerAdd($params): bool
    {
        $path = $params['plugin_path'] . 'component/controllerTpl.tpl';
        //顶级菜单
        $menu = $rootMenu = '';
        if (empty($params['menu_id'])) {
            $rootMenu = <<<H
            /**
              * Class {$params['controller']}Controller
              * @package app\admin\controller
              * @adminMenuRoot(
              *     'name'   =>'{$params['menu_name']}',
              *     'action' =>'index',
              *     'parent' =>'',
              *     'display'=> true,
              *     'order'  => 10000,
              *     'icon'   =>'',
              *     'remark' =>''
              * )
            */
H;
        } else {
            $menuInfo = AdminMenuModel::where('id', $params['menu_id'])->find();
            $menu_parent = $menuInfo['app'] . '/' . $menuInfo['controller'] . '/' . $menuInfo['action'];
            $menu = <<<H
            /**
              *列表
              * @adminMenu(
              *     'name'   => '{$params['menu_name']}',
              *     'parent' => '{$menu_parent}',
              *     'display'=> true,
              *     'hasView'=> false,
              *     'order'  => 10000,
              *     'icon'   => '',
              *     'remark' => '',
              *     'param'  => ''
              * )
            */
H;
        }

        $tpl = str_replace(['{$model}', '{$controller}', '{$adminMenu}', '{$menu}', '{$sqlTpl}'], [$params['model'], $params['controller'], $rootMenu, $menu, $params['sqlTpl']], file_get_contents($path));

        $controllerPath = app()->getRootPath() . 'app/admin/controller/' . $params['controller'] . 'Controller.php';
        $res = file_put_contents($controllerPath, $tpl);

        return $res ? true : false;
    }

    /**
     * 生成模型
     */
    protected function modelAdd($params)
    {
        $path = $params['plugin_path'] . 'component/modelTpl.tpl';

        $attrTpl = $getFTpl = '';
        foreach ($params['fileFields'] as $field) {
            $field = ucfirst($field);
            $attrTpl .= <<<E
                public function get{$field}Attr(\$v){
                    return \$v?explode(',',\$v):[''];
                }
E;

        }

        $tpl = str_replace(['{$model}', '{$attrTpl}'], [$params['model'], $attrTpl], file_get_contents($path));
        $modelPath = app()->getRootPath() . 'app/admin/model/' . $params['model'] . '.php';
        $res = file_put_contents($modelPath, $tpl);

        return $res ? true : false;
    }

    /**
     * 将驼峰转换为小写
     * @param $camelCaps
     * @param string $separator
     * @return string
     */
    protected function uncamelize($camelCaps, $separator = '_')
    {
        return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
    }

    /**
     * 将小写转换为驼峰
     * @param $camelCaps
     * @param string $separator
     * @return string
     */
    protected function camelize($data, $separator = '_')
    {
        $data = array_map(function ($v) {
            return ucfirst($v);
        }, explode($separator, $data));

        return implode('', $data);
    }

}